package com.zhengx.blehelper;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
 * name：BluetoothLeClient
 * class: 蓝牙连接基础类
 * author: zhengx
 * create_time: 2018/10/29
 */

public class BluetoothLeClient extends BluetoothGattCallback {

    private boolean isDebug = false;

    private static final String TAG = BluetoothLeClient.class.getSimpleName();

    private String HEART_BEAT = "h-b\r\n";

    private long heartBeatIntervalMillsec = 5 * 1000;

    private long latestWriteTime = 0;

    private Context context = null;

    private String mac;
    /*bluetooth le property*/

    private BluetoothGatt mBluetoothGatt;
    private BluetoothDevice mRemoteDevice;

    private BluetoothGattCharacteristic mNotifyCharacteristic;
    private BluetoothGattCharacteristic mWriteCharacteristic;

    /**
     * 提供给BleManager的回调接口
     */
    private BleCallback.BleGattCallback bleGattCallback;

    private ScheduledExecutorService timerReadRssiService;

    private boolean isConnected = false;

    private boolean shouldAutoReconnect = true;
    /**
     * 重连次数
     */
    private int reconnectCount;

    private UUID UUID_SERVICE = null;
    private UUID uuid_notify = null;
    private UUID uuid_write = null;

    private MessageDecoder notifyMessageDecoder;
    private MessageDecoder readMessageDecoder;

    private Handler mainHandler;

    private Runnable connectTimeoutRunnable = new Runnable() {
        @Override
        public void run() {
            if (!isConnected) {
                if (bleGattCallback != null) {
                    bleGattCallback.onConnectTimeout(BluetoothLeClient.this);
                }
            }
        }
    };

    public BluetoothLeClient(Context context,
                             BluetoothDevice bluetoothDevice) {
        initCxt(context.getApplicationContext());
        this.mRemoteDevice = bluetoothDevice;
    }

    private void initCxt(Context context) {
        this.context = context;
        mainHandler = new Handler();
        initDefaultMessageDecoder();
    }

    public BluetoothLeClient log(boolean logOpen) {
        isDebug = logOpen;
        return this;
    }
    /**
     * 设置要建立连接的uuid
     * @param uuidService
     * @param uuid_notify
     * @return
     */
    public BluetoothLeClient setDefaultCharacteristicsUUID(UUID uuidService, UUID uuid_notify,
                                                           UUID uuid_write) {
        if (uuidService == null || uuid_notify == null || uuid_write == null) {
            throw new IllegalArgumentException("DefaultCharacteristicsUUID不能为空");
        }
        this.UUID_SERVICE = uuidService;
        this.uuid_notify = uuid_notify;
        this.uuid_write = uuid_write;
        return this;
    }

    public BluetoothLeClient setHeartBeat(String heartBeat, long millsec) {
        this.heartBeatIntervalMillsec = millsec;
        this.HEART_BEAT = heartBeat;

        return this;
    }

    /**
     * 设置是否自动重连
     * @param shouldAutoReconnect
     * @return
     */
    public BluetoothLeClient setShouldReconnect(boolean shouldAutoReconnect) {

        this.shouldAutoReconnect = shouldAutoReconnect;
        return this;
    }

    /**
     * 设置蓝牙设备状态改变回调
     * @param gattCallback
     * @return
     */
    public BluetoothLeClient setGattCallback(BleCallback.BleGattCallback gattCallback) {
        this.bleGattCallback = gattCallback;
        return this;
    }

    public BluetoothLeClient setNotifyMessageDecoder(MessageDecoder notifyMessageDecoder) {
        if (notifyMessageDecoder != null) {
            notifyMessageDecoder.setOnReadMsgCallback(onNotifyMsgCallback);
        }
        this.notifyMessageDecoder = notifyMessageDecoder;
        return this;
    }

    public BluetoothLeClient setReadMessageDecoder(MessageDecoder readMessageDecoder) {
        if (readMessageDecoder != null) {
            readMessageDecoder.setOnReadMsgCallback(onReadMsgCallback);
        }
        this.readMessageDecoder = readMessageDecoder;
        return this;
    }

    public void switchHeartbeat(boolean enable) {
        if (!enable) {
            stopHeartbeat();
        } else {
            startHeartbeat();
        }
    }

    public void connect() {
        disconnect();
        closeBluetoothGatt();

        this.mac = mRemoteDevice.getAddress();
        mBluetoothGatt = mRemoteDevice.connectGatt(context, false, this);
        if (mBluetoothGatt != null) {
            mainHandler.removeCallbacks(connectTimeoutRunnable);
        }
    }
    /**
     * 连接扫描到的设备
     * @param device
     */
    public void connect(BluetoothDevice device) {
        this.mac = device.getAddress();
        mRemoteDevice = device;

        disconnect();
        closeBluetoothGatt();
        mBluetoothGatt = mRemoteDevice.connectGatt(context, false, this);
        if (mBluetoothGatt != null) {
            mainHandler.removeCallbacks(connectTimeoutRunnable);
        }
    }

    /**
     * 是否需要重连
     * @return
     */
    public boolean isShouldReConnect() {
        return shouldAutoReconnect;
    }

//    /**
//     * 返回重连次数
//     * @return
//     */
//    public int getReconnectCount() {
//        return reconnectCount;
//    }

    public void reconnecting() {
        if (shouldAutoReconnect) {
            reconnectCount ++;
            if (bleGattCallback != null) {
                bleGattCallback.onReConnect(this, reconnectCount);
            }
        }
    }
    /**
     * 重连
     */
    public void reConnect(final BluetoothDevice device) {

        if (shouldAutoReconnect) {
            connect(device);
        }
    }
    /**
     * 设置操作的特征协议
     * @param notifyCharacteristic
     */
    public void setOptionalCharacteristic(BluetoothGattService gattService,
                                          BluetoothGattCharacteristic notifyCharacteristic,
                                          BluetoothGattCharacteristic writeCharacteristic) {
        if (!isConnected()) {
            return;
        }

        UUID_SERVICE = gattService.getUuid();
        uuid_notify = notifyCharacteristic.getUuid();
        uuid_write = writeCharacteristic.getUuid();
        if (isDebug) {
            Log.i(TAG, "设置notify character：" + notifyCharacteristic.getUuid().toString());
            Log.i(TAG, "设置write character：" + writeCharacteristic.getUuid().toString());
        }

        mNotifyCharacteristic = notifyCharacteristic;
        mWriteCharacteristic = writeCharacteristic;

        setCharacteristicNotification(notifyCharacteristic, true);
        if (bleGattCallback != null) {
            bleGattCallback.onGattCharacteristicSet(BluetoothLeClient.this);
        }
        startHeartbeat();
    }

    public String getClientMac() {
        return mac;
    }

    public synchronized boolean sendSlicedMsg(byte[] bytes) {
        return sendSlicedMsg(bytes, 20, 20);
    }

    public synchronized boolean sendSlicedMsg(byte[] bytes, int packetLength,
                                              long intervalMillis) {

        return sendSlicedMsg(bytes, packetLength, intervalMillis, 3);
    }
    /**
     * 写入数据，分包发送
     * @param bytes
     * @param packetLength 每包长度
     * @param intervalMillis 每包间隔
     * @param retryCount 重试次数
     * @return
     */
    public synchronized boolean sendSlicedMsg(byte[] bytes, int packetLength,
                                              long intervalMillis, int retryCount) {
        latestWriteTime = System.currentTimeMillis();

        if(mWriteCharacteristic != null &&
                mBluetoothGatt != null){

            boolean ret = true;

            for (int i = 0; i < bytes.length; i += packetLength) {
                int nextIndex = i + packetLength;
                if (nextIndex > bytes.length) {
                    nextIndex = bytes.length;
                }
                byte[] slice = subBytes(bytes, i, nextIndex - i);

                int sendTimesCount = retryCount;
                while (sendTimesCount > 0) {
                    mWriteCharacteristic.setValue(slice);
                    boolean b = mBluetoothGatt.writeCharacteristic(mWriteCharacteristic);
                    if (isDebug) {
                        Log.i(TAG, "发送字节数组：\n16进制" + HexUtil.encodeHexStr(slice) +
                                "\n字符串:" + new String(slice) +
                                "\n 是否成功发出数据:"+b);
                    }
                    if (b) {
                        sendTimesCount = 0;
                    } else {
                        sendTimesCount --;
                    }

                    if (sendTimesCount == 0) {
                        ret = false;
                    }
                }
                if (nextIndex < bytes.length) {
                    try {
                        Thread.sleep(intervalMillis);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            return ret;
        }
        return false;
    }

    /**
     * 写入数据，不分包
     * @param bytes
     * @return
     */
    public synchronized boolean sendMsg(byte[] bytes) {
        latestWriteTime = System.currentTimeMillis();
        if(mWriteCharacteristic != null &&
                mBluetoothGatt != null){
            mWriteCharacteristic.setValue(bytes);
            boolean b = mBluetoothGatt.writeCharacteristic(mWriteCharacteristic);
            if (isDebug) {
                Log.i(TAG, "发送字节数组：\n16进制" + HexUtil.encodeHexStr(bytes) +
                        "\n字符串:" + new String(bytes) +
                        "\n 是否成功发出数据:"+b);
            }
            return b;
        }
        return false;
    }

    /**
     * 读取数据请求
     * @return
     */
    public boolean readCharacter() {
        if(mNotifyCharacteristic != null &&
                mBluetoothGatt != null){

            boolean b = mBluetoothGatt.readCharacteristic(mNotifyCharacteristic);
            if (isDebug) {
                Log.i(TAG, "读取数据："  + " 是否成功发出请求:"+b);
            }
            return b;
        }
        return false;
    }

    /**
     * 读取信号
     * @return
     */
    public boolean readRssi() {
        if(mNotifyCharacteristic != null &&
                mBluetoothGatt != null){

            boolean b = mBluetoothGatt.readRemoteRssi();
            if (isDebug) {
                Log.i(TAG, "读取信号："  + " 是否成功发出请求:"+b);
            }
            return b;
        }
        return false;
    }

    public void closeConnect() {

        if (mBluetoothGatt == null) {
            if (isDebug) {
                Log.e(TAG, "mBluetoothGatt not initialized");
            }
            return;
        }
        if (isConnected()) {
            mBluetoothGatt.disconnect();
        }
        stopHeartbeat();
    }

    /*is device connected*/
    public boolean isConnected() {
        return isConnected;
    }

    /*blue gatt callback*/
    /*系统蓝牙状态回调*/
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                onConnected();

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {

                onDisconnected();
            }
        } else {
            onDisconnected();
        }
    }

    private void onConnected() {
        isConnected = true;
        if (isShouldReConnect()) {
            reconnectCount = 0;
        }
        if (isDebug) {
            Log.i(TAG,"Thread info:" + Thread.currentThread().getName() +"\n" +
                    getmRemoteDevice().getName() + "==" + getmRemoteDevice().getAddress()
                    + " 连接状态——连接成功");
        }

        if (bleGattCallback != null) {
            bleGattCallback.onConnected(BluetoothLeClient.this);
        }
        //连接设备成功后查找服务并连接;获取设备信号强度

        boolean discoverRet = mBluetoothGatt.discoverServices();
        if (isDebug) {
            Log.i(TAG, "Attempting to start service discovery:"
                    + discoverRet);
        }
    }

    private void onDisconnected() {
        if (isDebug) {
            Log.i(TAG,"Thread info:" + Thread.currentThread().getName() +"\n" +
                    getmRemoteDevice().getName() + "==" + getmRemoteDevice().getAddress()
                    + " 连接状态——断开连接");
        }
        isConnected = false;
        disconnect();
        closeBluetoothGatt();
        stopHeartbeat();
        if (!isShouldReConnect()) {
            closeConnect();
        }
        if (bleGattCallback != null) {
            bleGattCallback.onDisconnected(BluetoothLeClient.this);
        }

    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        if (isDebug) {
            Log.i(TAG,"Thread info:" + Thread.currentThread().getName() +"\n" +
                    getmRemoteDevice().getName() + "==" + getmRemoteDevice().getAddress()
                    + " onServicesDiscovered" + "  status:" + status);
        }

        if (status == BluetoothGatt.GATT_SUCCESS) {

            if (bleGattCallback != null) {
                bleGattCallback.onServiceDiscovered(BluetoothLeClient.this, gatt.getServices());
            }
            setGattCharacteristicByDefaultUUID(gatt.getServices());

        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//        Log.e(TAG, "Thread info:" + Thread.currentThread().getName() +"\n" +
//                "onCharacteristicRead");
        if (status == BluetoothGatt.GATT_SUCCESS) {
            if (isDebug) {
                Log.i(TAG, "onCharacteristicRead success\n" +
                        "16进制：" + HexUtil.encodeHexStr(characteristic.getValue()) +
                "\n字符串：" + characteristic.getStringValue(0));
            }
        }
//        String string = HexUtil.encodeHexStr(characteristic.getValue());
        if (bleGattCallback != null) {
            bleGattCallback.onRead(BluetoothLeClient.this, MsgType.READ_SOURCE, characteristic.getValue());
        }

        if (notifyMessageDecoder != null) {
            notifyMessageDecoder.addMessage(characteristic.getValue());
        }
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//        String hexstring = HexUtil.encodeHexStr(characteristic.getValue());
        //Log.e(TAG, "on write status: " + status +"\n value:" +hexstring + "\n");
        if (bleGattCallback != null) {
            bleGattCallback.onWriteValue(BluetoothLeClient.this, characteristic.getValue());
        }
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
//        long now = System.currentTimeMillis();
        if (bleGattCallback != null) {
            bleGattCallback.onRead(BluetoothLeClient.this, MsgType.NOTIFY_SOURCE, characteristic.getValue());
        }
        if (isDebug) {
            String string = HexUtil.encodeHexStr(characteristic.getValue());
            Log.i(TAG, "Thread info:" + Thread.currentThread().getName() +"\n" +
                    "onCharacteristicChanged  notify value\n16进制:" +
                    string + "\n" + "字符串:" + characteristic.getStringValue(0));
        }

        if (notifyMessageDecoder != null) {
            notifyMessageDecoder.addMessage(characteristic.getValue());
        }
    }

    @Override
    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {

    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {

    }

    @Override
    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {

    }

    @Override
    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
        if (bleGattCallback != null) {
            bleGattCallback.onReadRemoteRssi(BluetoothLeClient.this, rssi, status == BluetoothGatt.GATT_SUCCESS);
        }
    }

    @Override
    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {

    }


    /**
     * 根据指定UUID连接相应服务
     * @param gattServices
     */
    private void setGattCharacteristicByDefaultUUID(List<BluetoothGattService> gattServices) {
        if (UUID_SERVICE == null || uuid_notify == null) {
            return;
        }
        if (isDebug) {
            StringBuilder stringBuilder = new StringBuilder();
            for (BluetoothGattService gattService : gattServices) {
                stringBuilder.append("=======service=======");
                stringBuilder.append("\n");
                stringBuilder.append(gattService.getUuid());
                stringBuilder.append("\n");
                List<BluetoothGattCharacteristic> characteristics = gattService.getCharacteristics();
                for (BluetoothGattCharacteristic characteristic : characteristics) {
                    stringBuilder.append("character:" );
                    stringBuilder.append(characteristic.getUuid());
                    stringBuilder.append("\n");
                }
            }
            Log.i(TAG, "services:" + stringBuilder.toString());
        }

        for (BluetoothGattService gattService : gattServices) {

            if(gattService.getUuid().toString().equalsIgnoreCase(this.UUID_SERVICE.toString())) {

                BluetoothGattCharacteristic notifyCharac = null, writeCharac = null;
                List<BluetoothGattCharacteristic> gattCharacteristics =
                        gattService.getCharacteristics();

                for (BluetoothGattCharacteristic gattCharacteristic :
                        gattCharacteristics) {

                    if(gattCharacteristic.getUuid().toString().equalsIgnoreCase(this.uuid_notify.toString())) {
                        notifyCharac = gattCharacteristic;
                    }
                    if (gattCharacteristic.getUuid().toString().equalsIgnoreCase(this.uuid_write.toString())) {
                        writeCharac = gattCharacteristic;
                    }
                }
                setOptionalCharacteristic(gattService, notifyCharac, writeCharac);
                return;
            }
        }
    }

    /**
     * Enables or disables notification on a give characteristic.
     *
     * @param characteristic Characteristic to act on.
     * @param enabled If true, enable notification.  False otherwise.
     */
    private void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                              boolean enabled) {
        if ( mBluetoothGatt == null) {
            if (isDebug) {
                Log.e(TAG, "mBluetoothGatt not initialized");
            }

            return;
        }
        boolean isNotiEnable = mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

        if (isNotiEnable) {
            List<BluetoothGattDescriptor> descriptorList = characteristic.getDescriptors();
            if(descriptorList != null && descriptorList.size() > 0) {
                for(BluetoothGattDescriptor descriptor : descriptorList) {
                    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                    mBluetoothGatt.writeDescriptor(descriptor);
                }
            }
        }
    }
    /**
     * 定时获取设备信号，每秒
     */
    private void startHeartbeat() {
        timerReadRssiService = Executors
                .newSingleThreadScheduledExecutor();
        // 第二个参数为首次执行的延时时间，第三个参数为定时执行的间隔时间
        if (timerReadRssiService.isTerminated() || timerReadRssiService.isShutdown()) {
            timerReadRssiService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    long currentTime = System.currentTimeMillis();
                    if (currentTime - latestWriteTime < heartBeatIntervalMillsec) {
                        stopHeartbeat();
                        startHeartbeat();
                        return;
                    }

                    if (!sendMsg(HEART_BEAT.getBytes())) {
                        if (isDebug) {
                            Log.e(TAG,"发送心跳包失败");
                        }
//                    stopHeartbeat();
                    }
                }
            }, 1, heartBeatIntervalMillsec, TimeUnit.MILLISECONDS);
        }
    }

    private void stopHeartbeat() {
        if (timerReadRssiService != null && !timerReadRssiService.isShutdown()) {
            timerReadRssiService.shutdown();
        }
    }

    /**
     * Disconnects an existing connection or cancel a pending connection. The disconnection result
     * is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    private void disconnect() {
        if (mBluetoothGatt == null) {
            if (isDebug) {
                Log.e(TAG, "mBluetoothGatt not initialized");
            }
            return;
        }
        if (isConnected()) {
            mBluetoothGatt.disconnect();
        }
    }

    /**
     * After using a given BLE device, the app must call this method to ensure resources are
     * released properly.
     */
    private void closeBluetoothGatt() {
        if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }

    public enum MsgType {
        READ_SOURCE,
        NOTIFY_SOURCE,
        NOTIFY,
        READ
    }

    protected BluetoothGatt getmBluetoothGatt() {
        return mBluetoothGatt;
    }

    protected BluetoothDevice getmRemoteDevice() {
        return mRemoteDevice;
    }

    protected BluetoothGattCharacteristic getmNotifyCharacteristic() {
        return mNotifyCharacteristic;
    }

    private BleCallback.OnReadMsgCallback onReadMsgCallback = new BleCallback.OnReadMsgCallback() {
        @Override
        public void onRead(byte[] msg) {
            if (bleGattCallback != null) {
                bleGattCallback.onRead(BluetoothLeClient.this, MsgType.READ, msg);
            }
        }
    };

    private BleCallback.OnReadMsgCallback onNotifyMsgCallback = new BleCallback.OnReadMsgCallback() {
        @Override
        public void onRead(byte[] msg) {
            if (bleGattCallback != null) {
                bleGattCallback.onRead(BluetoothLeClient.this, MsgType.NOTIFY, msg);
            }
        }
    };

    private void initDefaultMessageDecoder() {

        readMessageDecoder = new MessageDecoder() {
            @Override
            public boolean decode(byte[] source) {
                return true;
            }
        };
        readMessageDecoder.setOnReadMsgCallback(onReadMsgCallback);

        notifyMessageDecoder = new MessageDecoder() {
            @Override
            public boolean decode(byte[] source) {
                return true;
            }
        };
        notifyMessageDecoder.setOnReadMsgCallback(onNotifyMsgCallback);
    }

    private byte[] subBytes(byte[] src, int begin, int count) {
        byte[] bs = new byte[count];
        for (int i = begin; i < begin + count; i++) bs[i - begin] = src[i];
        return bs;
    }
}
